/* ops.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kernel.h"
#include "newfs.h"
#include "swis.h"

/***************************************************************************/

  /* A note on re-entrancy.

     In general, IFS entry points should be reentrant - since an image file
      may itself be stored as a file within another image - possibly of the
      same type.


     In the case of this simple NewFS implementation, it seems that the only
      possible re-entrancies are related to NewFS_get_bytes(..) and
      NewFS_put_bytes(..): that is, a call to transfer data on the image
      file can result in a reentrant call to transfer data on the image
      file in which the image file is contained. More precisely:

         FSw calls NewFS_get_bytes(fh1, ..) to read data from a file inside a
          NewFS image.

         NewFS calls OS_GBPB(fswh2, ..) to read data from the image file.

         FSw calls NewFS_get_bytes(fh2, ..) to read data from the image file
          that is, itself, a file inside a NewFS image.

         NewFS calls OS_GBPB(fswh3, ..) to read data from the enclosing image
          file.

         FSw calls FSEntry_GetBytes(fh3, ..) to read the desired data from
          the underlying HFS>

     Note also that it is possible that FSw may call NewFS_write_bytes(..) in
      response to a read - and vice-versa, depending on FSw's current buffer
      state.


     There is one other possibility of re-entrancy, but it does not appear
      to occur in practice.

     When FSw calls NewFS_image_open(..) to register a new image file, NewFS
      calls OS_Args(2, ..) to find out how big the image is. We might expect
      this to result in a call of NewFS_read_cat(..), but it seems that FSw
      maintains extent information - initially provided by the open call -
      and so does not need to reenter the IFS.

  */

/***************************************************************************/

  /* static parameter blocks to return values to FileSwitch */

FS_open_block open_file_PB;
FS_datestamp time_stamp_PB;
FS_dir_entry dir_entry_PB;
FS_dir_block dir_read_PB;
FS_free_space space_info_PB;


/***************************************************************************/

static BOOL check_same(image_handle ih, int pos, void *handle)

  /* Called during enumeration to see if the current object has the same
      name as (char*)handle.
  */

{
  char *base_name = (char*)handle;

  read_obj_name(ih, pos);

  if (equal_names(curr_obj_name.name, base_name))
  {
    read_obj_hdr(ih, pos);
    return TRUE;
  }

  return FALSE;
}

/***************************************************************************/

FS_open_block *NewFS_open_file      /* ImageEntry_Open - page 2-531 */
(
  int reason,
  char *file_name,
  image_handle ih
)

{
  fcb *new_fcb;

  debug("Open_file: '%s' in image &%08x, reason = %d, \n",
        file_name, ih, reason);

 /* check reason code */
  if (reason != OPEN_FOR_READ &&
      reason != OPEN_FOR_UPDATE)
  {
    global_errorV("Unknown open reason code %d", reason);
    return (FS_open_block*)(-1);
  }

 /* locate the file */
  for_each_object(ih, check_same, (void*)file_name);

 /* check that open request is compatible with access permissions */
  {
    int access_mask = (reason==OPEN_FOR_READ) ? ATTR_READ : ATTR_WRITE;

    if ((curr_obj_hdr.attributes & access_mask) == 0)
    {
      global_errorV("Access denied");
      return (FS_open_block*)(-1);
    }
  }

 /* check that this request to open is compatible with any other current
     openings */
  {
    file_handle fh = file_list;

    while (fh != NULL)
    {
     /* if this file is already open ... */
      if (fh->ih == ih && fh->hdr_pos == curr_obj_pos)
      {
       /* ... then another open for update is forbidden */
        if (reason == OPEN_FOR_UPDATE)
        {
          global_errorV("File already open");
          return (FS_open_block*)(-1);
        }

       /* ... for update, then no further open is permitted */
        if (fh->reason == OPEN_FOR_UPDATE)
        {
          global_errorV("File already open for update");
          return (FS_open_block*)(-1);
        }
      }

      fh = fh->next;
    }
  }

 /* create file control block and link in to list */
  new_fcb = check_alloc(sizeof(fcb));

  new_fcb->ih = ih;
  new_fcb->reason = reason;
  new_fcb->hdr_pos = curr_obj_pos;
  new_fcb->pos = curr_obj_pos +
                 curr_obj_hdr.size +
                 curr_obj_name.size + 4;

  new_fcb->next = file_list;
  file_list = new_fcb;

  debug("  - assigned file handle &%08x\n", (int)new_fcb);

 /* fill in parameter block to return */
  open_file_PB.file_info_word = OPEN_ATTRS;
  open_file_PB.handle = new_fcb;
  open_file_PB.buff_size = ih->hdr.block_size;
  open_file_PB.extent = curr_obj_hdr.extent;
  read_bytes(ih, (char*)&open_file_PB.allocated, 4, new_fcb->pos-4);
  open_file_PB.allocated -= 4;

  return &open_file_PB;
}

/***************************************************************************/

int NewFS_get_bytes                 /* ImageEntry_GetBytes - page 2-534 */
(
  file_handle fh,
  char *buff,
  int n,
  int offset
)

{
  debug("Get_bytes: read %d bytes at %d in file &%08x to buff at &%08x\n",
        n, offset, (int)fh, (int)buff);

  read_bytes(fh->ih, buff, n, fh->pos + offset);

  return 0;
}

/***************************************************************************/

int NewFS_put_bytes                 /* ImageEntry_PutBytes - page 2-536 */
(
  file_handle fh,
  char *buff,
  int n,
  int offset,
  char dbyte
)

{
  debug("Put_bytes: write %d bytes at %d in file &%08x from buff at &%08x\n",
        n, offset, (int)fh, (int)buff);

  write_bytes(fh->ih, buff, n, fh->pos + offset);

  return 0;
}

/***************************************************************************/

int NewFS_close_file                /* ImageEntry_CloseFile - page 2-545 */
(
  file_handle fh,
  int load,
  int exec
)

{
  image_handle ih = fh->ih;
  BOOL flush_image = fh->reason == OPEN_FOR_UPDATE;

  debug("Close_file: handle = &%08x, load = &%08x, exec = &%08x\n",
        (int)fh, load, exec);

 /* update load, exec if necessary */
  if (load != 0 || exec != 0)
  {
    int hdr_pos = fh->hdr_pos;

    read_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), hdr_pos);

    if (load != 0) curr_obj_hdr.load = load;
    if (exec != 0) curr_obj_hdr.exec = exec;

    write_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), hdr_pos);
  }

 /* remove fcb from list of open files */
  {
    file_handle *prev = &file_list;

    while (*prev != fh)
      prev = &((*prev)->next);

    *prev = fh->next;
  }

 /* free fcb */
  check_free(fh);

 /* if necessary, ask FileSwitch to flush this image */
  if (flush_image)
    _swix(OS_Args, I0|I1,
          255,              /* flush file */
          ih->fswh          /* FileSwitch handle */
         );

  return 0;
}

/***************************************************************************/

int NewFS_write_extent              /* ImageEntry_Args 3 - page 2-540 */
(
  file_handle fh,
  int extent
)

{
  image_handle ih = fh->ih;
  int hdr_pos = fh->hdr_pos;

  debug("Write_extent: write %d to file handle &%08x\n",
        extent, (int)fh);

 /* update file header with new extent value */
  read_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), hdr_pos);
  curr_obj_hdr.extent = extent;
  write_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), hdr_pos);

  return 0;
}

/***************************************************************************/

int NewFS_alloc                     /* ImageEntry_Args 4 - page 2-541 */
(
  file_handle fh
)

{
  int content_size;

  debug("Alloc: file handle &%08x\n", (int)fh);

 /* read object content size to obtain allocation information */
  read_bytes(fh->ih, (char*)&content_size, 4, fh->pos-4);

  debug(" - allocation = %d\n", content_size-4);

  return content_size-4;
}

/***************************************************************************/

FS_datestamp *NewFS_flush           /* ImageEntry_Args 6 - page 2-542 */
(
  file_handle fh
)

  /* This need only be supported if bit 27 = 1 in the IFSIW - and this is
      only done when the IFS buffers data; this IFS does not buffer data
      of any kind!
  */

{
  debug("Flush called for file handle &%08x\n", (int)fh);

  global_errorV("NewFS does not support Flush");

  return (FS_datestamp*)(-1);
} 

/***************************************************************************/

int NewFS_ensure                    /* ImageEntry_Args 7 - page 2-543 */
(
  file_handle fh,
  int new_alloc
)

{
  image_handle ih = fh->ih;
  int pos = fh->pos;
  int image_size = ih->hdr.image_size;
  int old_alloc;
  int delta;

  debug("Ensure: allocate %d for file handle &%08x\n",
        new_alloc, (int)fh);

 /* read current allocation */
  read_bytes(ih, (char*)&old_alloc, 4, pos-4);
  old_alloc -= 4;

 /* set delta to increase in size required */
  delta = new_alloc - old_alloc;

 /* return if it's big enough already */
  if (delta <= 0)
    return 0;

 /* check there is sufficient space in the image file */
  if (ih->file_size < (image_size + delta))
  {
    global_errorV("Insufficient space to extend file");
    return -1;
  }

 /* move any objects above this file to make space */
  {
    int old_pos = pos + old_alloc;

    if (old_pos != image_size)
      copy_up(ih, old_pos, delta, image_size-old_pos);
  }

 /* store the new allocation */
  new_alloc += 4;
  write_bytes(ih, (char*)&new_alloc, 4, pos-4);

 /* and update and write back the image header */
  ih->hdr.image_size += delta;
  write_bytes(ih, (char*)&(ih->hdr), sizeof(image_hdr), 0);

  debug("  - %d allocated\n", new_alloc-4);

  return new_alloc-4;
}

/***************************************************************************/

int NewFS_write_zeros               /* ImageEntry_Args 8 - page 2-543 */
(
  file_handle fh,
  int offset,
  int size
)

{
  image_handle ih = fh->ih;
  int pos = fh->pos + offset;
  int i;

  debug("Write_zeros: write %d zeros at %d to file handle &%08x\n",
        size, offset, (int)fh);

 /* clear the copy buffer */
  for (i=0; i<COPY_BUFF_SIZE; i++)
    copy_buffer[i] = 0;

  while (size > 0)
  {
    int n = COPY_BUFF_SIZE;

    if (n > size)
      n = size;

    write_bytes(ih, (char*)copy_buffer, n, pos);

    pos += n;
    size -= n;
  }

  return 0;
}

/***************************************************************************/

FS_datestamp *NewFS_read_datestamp  /* ImageEntry_Args 9 - page 2-544 */
(
  file_handle fh
)

{
  debug("Read_datestamp: file handle &%08x\n", (int)fh);

 /* read object header */
  read_bytes(fh->ih, (char*)&curr_obj_hdr, sizeof(object_hdr), fh->hdr_pos);

 /* fill in result parameter block */
  time_stamp_PB.load = curr_obj_hdr.load;
  time_stamp_PB.exec = curr_obj_hdr.exec;

  debug("  - load = &%08x, exec = &%08x\n",
        time_stamp_PB.load, time_stamp_PB.exec);

  return &time_stamp_PB;
}

/***************************************************************************/

static BOOL check_dir_exists(image_handle ih, char *path_name)

  /* Returns TRUE iff the directory containing the object specified by
      path_name exists
  */

{
  char *p = path_name + strlen(path_name);
  BOOL exists;

  do p--; while (p != path_name && *p != '.');

 /* if path_name specifies object in root directory, all is well */
  if (p == path_name)
    return TRUE;

  *p = 0;
  exists = for_each_object(ih, check_same, (void*)path_name);
  *p = '.';

  return exists;
}

/***************************************************************************/

char *create_file
(
  char *file_name,
  int load,
  int exec,
  char *base,
  char *end,
  image_handle ih,
  BOOL copy
)

  /* Called by NewFS_save_file (copy = TRUE) and NewFS_create (copy = FALSE)
      to create a new file.
  */

{
  int attributes = FILE_ATTR_DEFAULT;
  int extent = (end-base);
  int content_size = round_up_to_block(ih, extent) + 4;
  BOOL pre_exists;

 /* check that the directory to contain the object exists; error if not */
  if (!check_dir_exists(ih, file_name))
  {
    global_errorV("Directory for new file does not exist");
    return (char*)(-1);
  }

 /* does some object with this name already exist? */
  pre_exists = for_each_object(ih, check_same, (void*)file_name);

  if (pre_exists)  /* one has been found */
  {
    if (curr_obj_hdr.type == TYPE_DIR)  /* error if it's a directory */
    {
      global_errorV("Directory with this name already exists");
      return (char*)(-1);
    }

   /* if it's locked, raise error */
    if ((curr_obj_hdr.attributes & ATTR_LOCKED) != 0)
    {
      global_errorV("File with this name exists and is locked");
      return (char*)(-1);
    }

   /* if it's open, raise error */
    {
      file_handle fh = file_list;

      while (fh != NULL)
      {
        if (fh->ih == ih && fh->hdr_pos == curr_obj_pos)
        {
          global_errorV("Object with this name exists and is open");
          return (char*)(-1);
        }
        fh = fh->next;
      }
    }

   /* will there be sufficient space after deletion for the new file? */
    {
      int old_size;
      int pos = curr_obj_pos + sizeof(object_hdr);
      int n;

      read_bytes(ih, (char*)&n, 4, pos);
      read_bytes(ih, (char*)&old_size, 4, pos+n);

      if (ih->hdr.image_size + (content_size - old_size) > ih->file_size)
      {
        global_errorV("Insufficient space for new file");
        return (char*)(-1);
      }
    }

   /* there's enough space - so make a note of the attributes and delete */
    attributes = curr_obj_hdr.attributes;
    delete_object(ih, curr_obj_pos);
  }

 /* create the file details in curr_obj_hdr and curr_obj_name */
  curr_obj_hdr.size = sizeof(object_hdr);
  curr_obj_hdr.load = load;
  curr_obj_hdr.exec = exec;
  curr_obj_hdr.attributes = attributes;
  curr_obj_hdr.extent = extent;
  curr_obj_hdr.type = TYPE_FILE;

  curr_obj_name.size = round_up_to_word(strlen(file_name) + 5);
  strcpy(curr_obj_name.name, file_name);

 /* add this to the end of the image file */
  {
    int pos = ih->hdr.image_size;
    int size = curr_obj_hdr.size + curr_obj_name.size + content_size;

   /* raise error if no space - (check already done if pre_exists) */
    if (!pre_exists && pos+size > ih->file_size)
    {
      global_errorV("Not enough space for file");
      return (char*)(-1);
    }

   /* write out new object header and object name */
    write_bytes(ih, (char*)&curr_obj_hdr, curr_obj_hdr.size, pos);
    pos += curr_obj_hdr.size;
    write_bytes(ih, (char*)&curr_obj_name, curr_obj_name.size, pos);
    pos += curr_obj_name.size;
    write_bytes(ih, (char*)&content_size, 4, pos);

   /* write out contents if a "save" */
    if (copy)
      write_bytes(ih, base, extent, pos+4);

   /* update image header information */
    ih->hdr.image_size = pos + content_size;

   /* and write this out, too! */
    write_bytes(ih, (char*)&(ih->hdr), sizeof(image_hdr), 0);
  }

  if (copy)    /* must return file's leaf name for "save" */
  {
    char *leaf = strrchr(file_name, '.');

    return (leaf == NULL) ? file_name : leaf;
  }
  else
    return NULL;
}

/***************************************************************************/

char *NewFS_save_file               /* ImageEntry_File 0 - page 2-546 */
(
  char *file_name,
  int load,
  int exec,
  char *base,
  char *end,
  image_handle ih
)

{
  char *res;

  debug("Save_file: '%s' to image &%08x from &%08x to &%08x, load = &%08x, exec = &%08x\n",
        file_name, (int)ih, (int)base, (int)end, load, exec);

  res = create_file(file_name, load, exec, base, end, ih, TRUE);

  debug("  - returned '%s'\n", res);

  return res;
}

/***************************************************************************/

int NewFS_write_cat                 /* ImageEntry_File 1 - page 2-547 */
(
  char *obj_name,
  int load,
  int exec,
  int attributes,
  image_handle ih
)

{
  debug("Write_cat: '%s' in image &%08x, load = &%08x, exec = &%08x, attributes = &%08x\n",
        obj_name, ih, load, exec, attributes, (int)ih);

 /* locate the object and update object header */
  if (for_each_object(ih, check_same, (void*)obj_name))
                                         /* do nothing if object not found */
  {
    curr_obj_hdr.load = load;
    curr_obj_hdr.exec = exec;
    curr_obj_hdr.attributes = attributes;

    write_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), curr_obj_pos);
  }

  return 0;
}

/***************************************************************************/

FS_dir_entry *NewFS_read_cat        /* ImageEntry_File 5 - page 2-549 */
(
  char *obj_name,
  image_handle ih
)

{
  debug("Read_cat: '%s' in image &%08x\n",
        obj_name, ih);

  if (!for_each_object(ih, check_same, (void*)obj_name))
    dir_entry_PB.obj_type = TYPE_NOTFOUND;  /* doesn't exist */
  else
  {
    dir_entry_PB.obj_type = curr_obj_hdr.type;
    dir_entry_PB.load = curr_obj_hdr.load;
    dir_entry_PB.exec = curr_obj_hdr.exec;
    dir_entry_PB.length = curr_obj_hdr.extent;
    dir_entry_PB.attributes = curr_obj_hdr.attributes;
  }

  debug("  - type = %d, load = &%08x, exec = &%08x, extent = %d, attributes = &%08x\n",
        dir_entry_PB.obj_type, dir_entry_PB.load, dir_entry_PB.exec,
        dir_entry_PB.length, dir_entry_PB.attributes);

  return &dir_entry_PB;
}

/***************************************************************************/

static BOOL check_for_child(image_handle ih, int pos, void *handle)

  /* Returns TRUE iff the current object's name has (char*)handle as a
      prefix.
  */

{
  char *base_name = (char*)handle;
  int len = strlen(base_name);

  read_obj_name(ih, pos);

  return (equal_prefixes(base_name, curr_obj_name.name, len) &&
          curr_obj_name.name[len] == '.');
}

/***************************************************************************/

FS_dir_entry *NewFS_delete          /* ImageEntry_File 6 - page 2-550 */
(
  char *name,
  image_handle ih
)

{
  debug("Delete: '%s' from image &%08x\n", name, (int)ih);

 /* locate object to delete */
  if (!for_each_object(ih, check_same, (void*)name))
   /* object does not exist */
  {
    dir_entry_PB.obj_type = TYPE_NOTFOUND;
    return &dir_entry_PB;
  }

 /* raise error if object is locked */
  if ((curr_obj_hdr.attributes & ATTR_LOCKED) != 0)
  {
    global_errorV("Cannot delete locked object");
    return (FS_dir_entry*)(-1);
  }

 /* if it's open, raise error */
  {
    file_handle fh = file_list;

    while (fh != NULL)
    {
      if (fh->ih == ih && fh->hdr_pos == curr_obj_pos)
      {
        global_errorV("Cannot delete open object");
        return (FS_dir_entry*)(-1);
      }
      fh = fh->next;
    }
  }

 /* copy object header details to result parameter block */
  dir_entry_PB.obj_type = curr_obj_hdr.type;
  dir_entry_PB.load = curr_obj_hdr.load;
  dir_entry_PB.exec = curr_obj_hdr.exec;
  dir_entry_PB.length = curr_obj_hdr.extent;
  dir_entry_PB.attributes = curr_obj_hdr.attributes;

 /* if object is a directory, must check that it is empty */
  if (curr_obj_hdr.type == TYPE_DIR)
  {
    int objpos = curr_obj_pos;   /* preserve directory's position */

    if (for_each_object(ih, check_for_child, (void*)name))
    {
      global_errorV("Cannot delete non-empty directory");
      return (FS_dir_entry*)(-1);
    }

    curr_obj_pos = objpos;       /* restore directory's position */
  }

 /* delete the object */
  delete_object(ih, curr_obj_pos);

  debug("  - type = %d, load = &%08x, exec = &%08x, extent = %d, attributes = &%08x\n",
        dir_entry_PB.obj_type, dir_entry_PB.load, dir_entry_PB.exec,
        dir_entry_PB.length, dir_entry_PB.attributes);

  return &dir_entry_PB;
}

/***************************************************************************/

int NewFS_create                    /* ImageEntry_File 7 - page 2-550 */
(
  char *file_name,
  int load,
  int exec,
  char *base,   /* the file's length = end-base */
  char *end,
  image_handle ih
)

{
  debug("Create: '%s' to image &%08x from &%08x to &%08x, load = &%08x, exec = &%08x\n",
        file_name, (int)ih, (int)base, (int)end, load, exec);

  return (int)(create_file(file_name, load, exec, base, end, ih, FALSE));
}

/***************************************************************************/

static BOOL create_dir(image_handle ih, char *dir_name, int load, int exec)

  /* Creates a new object at the end of the image file which is a directory
      with the given name.

     Returns TRUE unless there is insufficient space available, in which
      case the result is FALSE.
  */

{
 /* create the directory details in curr_obj_hdr and curr_obj_name */
  curr_obj_hdr.size = sizeof(object_hdr);
  curr_obj_hdr.load = load;
  curr_obj_hdr.exec = exec;
  curr_obj_hdr.attributes = DIR_ATTR_DEFAULT;
  curr_obj_hdr.extent = 0;
  curr_obj_hdr.type = TYPE_DIR;

  curr_obj_name.size = round_up_to_word(strlen(dir_name) + 5);
  strcpy(curr_obj_name.name, dir_name);

 /* add this to the end of the image file, if there is space */
  {
    int pos = ih->hdr.image_size;
    int contents = 4;
    int size = curr_obj_hdr.size + curr_obj_name.size + 4;

   /* return FALSE if insufficient space */
    if (pos+size > ih->file_size)
      return FALSE;

   /* write out new object */
    write_bytes(ih, (char*)&curr_obj_hdr, curr_obj_hdr.size, pos);
    pos += curr_obj_hdr.size;
    write_bytes(ih, (char*)&curr_obj_name, curr_obj_name.size, pos);
    pos += curr_obj_name.size;
    write_bytes(ih, (char*)&contents, 4, pos);

   /* update image header information */
    ih->hdr.image_size = pos+4;

   /* and write this out, too! */
    write_bytes(ih, (char*)&(ih->hdr), sizeof(image_hdr), 0);
  }

  return TRUE;
}

/***************************************************************************/

int NewFS_create_dir                /* ImageEntry_File 8 - page 2-551 */
(
  char *dir_name,
  int load,
  int exec,
  int size,
  image_handle ih
)

{
  debug("Create_dir: '%s' in image &%08x, load = &%08x, exec = &%08x, size = %d\n",
        dir_name, (int)ih, load, exec, size);

 /* check that the directory to contain the object exists; error if not */
  if (!check_dir_exists(ih, dir_name))
  {
    global_errorV("Directory for new directory does not exist");
    return -1;
  }

 /* see if an object of the same name exists */
  if (for_each_object(ih, check_same, (void*)dir_name))
   /* one has been found */
  {
    if (curr_obj_hdr.type == TYPE_DIR)   /* ok if it's a directory */
      return 0;
    else
    {
      global_errorV("%s already exists as a file", dir_name);
      return -1;
    }
  }

 /* attempt to create the directory - error if insufficient space */
  if (!create_dir(ih, dir_name, load, exec))
  {
    global_errorV("Not enough space for directory");
    return -1;
  } 

  return 0;
}

/***************************************************************************/

int NewFS_read_block_size           /* ImageEntry_File 10 - page 2-552 */
(
  char *file_name,
  image_handle ih
)

  /* Note that in the case of NewFS, the block size is independent of
      the file.
  */

{
  debug("Read_block_size: '%s' in image &%08x\n", file_name, (int)ih);

  debug("  - block_size = %d\n", ih->hdr.block_size);

  return ih->hdr.block_size;
}

/***************************************************************************/

  /* This typedef defines the structure used to communicate between
      check_for_rename(..)  and NewFS_rename(..) */

typedef struct renamestr
{
  char *old_name;
  int old_len;
  char *new_name;
} renamestr;

/***************************************************************************/

static BOOL rename_current_object(image_handle ih,
                                  char *old_name, char *new_name)

  /* Rename the current object if possible; if there is not enough space,
      return FALSE.

     'old_name' is a prefix (possibly all) of the current object's name,
      which is to be replaced by 'new_name'.
  */

{
  int old_len, new_len;
  int obj_name_pos = curr_obj_pos + sizeof(object_hdr);

 /* careful, here! */
  old_len = curr_obj_name.size;
  new_len = round_up_to_word
            (
             strlen(curr_obj_name.name) +
             (strlen(new_name) - strlen(old_name)) + 
             5
            );

 /* move items above the object name section to make exactly the correct
     amount of space available for the new name */
  {
    int old_pos = obj_name_pos + old_len;
    int size = ih->hdr.image_size - old_pos;

   /* copy */
    if (old_len < new_len)
    {
     /* if insufficient space is available return FALSE */
      if (ih->hdr.image_size + (new_len-old_len) > ih->file_size)
        return FALSE;

      copy_up(ih, old_pos, new_len-old_len, size);
    }
    else
    if (old_len > new_len)
      copy_down(ih, old_pos, old_len-new_len, size);

   /* adjust image header and write back */
    ih->hdr.image_size += (new_len-old_len);
    write_bytes(ih, (char*)&(ih->hdr), sizeof(image_hdr), 0);
  }

 /* construct new object name section */
  {
    int old_p, new_p;
    int old_q, new_q;
    int i;
    int diff;

   /* these point to just after the prefix */
    old_p = strlen(old_name);
    new_p = strlen(new_name);

   /* and these to the name terminator */
    old_q = strlen(curr_obj_name.name);
    new_q = old_q + (new_p - old_p);

   /* copy suffix to correct position in name field */
    if (old_p > new_p)
    {
      diff = old_p - new_p;
      for (i=old_p; i<=old_q; i++)
        curr_obj_name.name[i-diff] = curr_obj_name.name[i];
    }
    else
    if (old_p < new_p)
    {
      diff = new_p - old_p;
      for (i=old_q; i>=old_p; i--)
        curr_obj_name.name[i+diff] = curr_obj_name.name[i];
    }

   /* copy new prefix in */
    memcpy(curr_obj_name.name, new_name, new_p);
  }
  curr_obj_name.size = new_len;

 /* and write it out */
  write_bytes(ih, (char*)&curr_obj_name, new_len, obj_name_pos);

  return TRUE;   /* rename successful */
}

/***************************************************************************/

static BOOL check_for_rename(image_handle ih, int pos, void *handle)

{
  renamestr *rstr = (renamestr*)handle;
  char *rename_old_name = rstr->old_name;
  char *rename_new_name = rstr->new_name;
  int len_old_name = rstr->old_len;

  read_obj_name(ih, pos);

  if (equal_prefixes(rename_old_name, curr_obj_name.name, len_old_name) &&
      curr_obj_name.name[len_old_name] == '.')
    return (!rename_current_object(ih, rename_old_name, rename_new_name));

  return FALSE;   /* on to the next one */
}

/***************************************************************************/

int NewFS_rename                    /* ImageEntry_Func 8 - page 2-559 */
(
  char *old_name,
  char *new_name,
  image_handle ih
)

{
  debug("Rename: '%s' to '%s' in image &%08x\n",
        old_name, new_name, (int)ih);

 /* check that the directory to contain the new object exists */
  if (!check_dir_exists(ih, new_name))
  {
    global_errorV("Directory for new name does not exist");
    return -1;
  }

 /* locate object */
  if (!for_each_object(ih, check_same, (void*)old_name))
  {
    global_errorV("Object to rename does not exist");  /* can never happen */
    return -1;
  }

 /* rename object if possible - error otherwise */
  if (!rename_current_object(ih, old_name, new_name))
  {
    global_errorV("Not enough space to rename");
    return -1;
  }

 /* if object is a directory, must now rename all objects within that
     directory */
  if (curr_obj_hdr.type == TYPE_DIR)
  {
    renamestr rstr;

   /* try to rename all objects inside the directory */
    rstr.old_name = old_name;
    rstr.old_len = strlen(old_name);
    rstr.new_name = new_name;
    if (!for_each_object(ih, check_for_rename, (void*)&rstr))
      return 0;     /* all completed without problem */

   /* not enough space to complete rename - must undo everything */
    rstr.old_name = new_name;
    rstr.old_len = strlen(new_name);
    rstr.new_name = old_name;
    for_each_object(ih, check_for_rename, (void*)&rstr);
     /* this must succeed */

   /* and undo the original directory rename itself */
    for_each_object(ih, check_same, (void*)new_name);
    rename_current_object(ih, new_name, old_name);

   /* and return error */
    global_errorV("Insufficient space to rename descendants");
    return -1;
  }

  return 0;
}

/***************************************************************************/

  /* This typedef defines the structure used to communicate amongst
      scan_dir(..), check_object(..) and store_info(..) or store_name(..).

     Together this set of functions implements the two FileSwitch calls used
      to read directory entries.
  */

typedef struct dirinfostr
{
  char *name;      /* name of directory being scanned */
  BOOL found;      /* set TRUE as soon as the directory to be
                       scanned is found */
  char *buff;      /* results are stored here; incremented as data
                       is added */
  int spare;       /* space remaining in buff; decremented as data
                       is added */
  int num_to_read; /* number of entries requested; decremented as
                       data is added */
  int first;       /* index of the first entry to be read */
  BOOL (*store_func)(char **buff, int *spare);
                   /* will be either store_info or store_name */
} dirinfostr;

/***************************************************************************/

static BOOL store_info(char **buff, int *spare)

  /* If sufficient space is available, copy information about the current
      object into the buffer and return TRUE; if not, return FALSE.
  */

{
  char *leaf = strrchr(curr_obj_name.name, '.');  /* locate last '.' */
  int n;

  if (leaf == NULL)
    leaf = curr_obj_name.name;  /* no dot */
  else
    leaf++;                     /* skip dot */

  n = round_up_to_word(
        sizeof(FS_entry_info) - 4 +   /* ignore char[3] name field */
        strlen(leaf) +                /* chars in name */
        1                             /* and terminator */
                      );

  *spare -= n;

  if (*spare < 0)
    return FALSE;

  {
    FS_entry_info *p = (FS_entry_info*)*buff;

    p->load = curr_obj_hdr.load;
    p->exec = curr_obj_hdr.exec;
    p->length = curr_obj_hdr.extent;
    p->attributes = curr_obj_hdr.attributes;
    p->obj_type = curr_obj_hdr.type;
    strcpy(p->name, leaf);
  }

  *buff += n;

  return TRUE;
}

/***************************************************************************/

static BOOL store_name(char **buff, int *spare)

  /* If sufficient space is available, copy the current object's leaf name
      into the buffer and return TRUE; if not, return FALSE.
  */

{
  char *leaf = strrchr(curr_obj_name.name, '.');  /* locate last '.' */
  int n;

  if (leaf == NULL)
    leaf = curr_obj_name.name;  /* no dot */
  else
    leaf++;

  n = strlen(leaf) + 1;

  *spare -= n;

  if (*spare < 0 ||
      !validate_buffer((int)*buff, (int)*buff+n))
       /* in case its an OS_GBPB 8 call where the length is not known */
    return FALSE;

  strcpy(*buff, leaf);
  *buff += n;

  return TRUE;
}

/***************************************************************************/

static BOOL check_object(image_handle ih, int pos, void *handle)

  /* Called for each object in the image file.

     If the object's name is the same as dir_name, then dir_found is set
      TRUE - provided that the object *is* a directory.

     If the object's name includes dir_name as a prefix, then store_func(..)
      is called to store information about the object.
  */

{
  dirinfostr *dstr = (dirinfostr*)handle;
  char *dir_name = dstr->name;

  read_obj_hdr_and_name(ih, pos);

 /* is this the directory itself? */
  if (!dstr->found &&
      equal_names(curr_obj_name.name, dir_name) &&
      curr_obj_hdr.type == TYPE_DIR)
  {
    dstr->found = TRUE;
    return FALSE;     /* and on to the next object */
  }

 /* is this object inside the directory? */
  if (is_in_dir(dir_name, curr_obj_name.name))
  {
   /* skip objects until we reach the first one to process */
    if (dstr->first != 0)
    {
      dstr->first--;
      return FALSE;
    }

    if (dstr->store_func(&dstr->buff, &dstr->spare))
     /* if info stored successfully */
    {
      dstr->num_to_read--;
      return (dstr->num_to_read == 0);
    }
    else
      return TRUE;
  }

 /* on to next object */
  return FALSE;
}

/***************************************************************************/

static FS_dir_block *scan_dir
(
  char *name,
  char *buff,
  int num,
  int first,
  int length,
  image_handle ih,
  BOOL f(char **buff, int *spare)
)

  /* Called by  NewFS_read_dir      - with f = store_name
        and by  NewFS_read_dir_info - with f = store_info
  */

{
  BOOL more_to_look_at;
  dirinfostr dstr;

 /* initialise variables for communication with check_object(..) and
     store_name(..) or store_info(..) */

  dstr.name = name;
  dstr.found = (strcmp(name, "") == 0);
   /* "" => root directory, which always exists */

  dstr.buff = buff;
  dstr.spare = length;
  dstr.num_to_read = num;
  dstr.first = first;

  dstr.store_func = f;

  more_to_look_at = for_each_object(ih, check_object, (void*)&dstr);

 /* error if directory was not found */
  if (!dstr.found)
  {
    global_errorV("%s does not exist or is a file", name);
    return (FS_dir_block*)(-1);
  }

 /* fill in result block */
  dir_read_PB.num_read = num - dstr.num_to_read;
  if (dstr.num_to_read == 0 || !more_to_look_at)
    dir_read_PB.next_offset = -1;      /* no more to read */
  else
    dir_read_PB.next_offset = first + dir_read_PB.num_read;

  return &dir_read_PB;
}

/***************************************************************************/

FS_dir_block *NewFS_read_dir        /* ImageEntry_Func 14 - page 2-563 */
(
  char *name,         /* name of directory */
  char *buff,         /* buffer for results */
  int num,            /* number of entries to read */
  int first,          /* starting with this entry */
  int length,         /* buffer length */
  image_handle ih
)

{
  FS_dir_block *res;

  debug("Read_dir: '%s' in image &%08x; buff at &%08x, length = %d; read %d entries starting at %d\n",
        name, (int)ih, (int)buff, length, num, first);

  res = scan_dir(name, buff, num, first, length, ih, store_name);

  debug("  - num_read = %d, next_to_read = %d\n",
         res->num_read, res->next_offset);

  return res;
}

/***************************************************************************/

FS_dir_block  *NewFS_read_dir_info  /* ImageEntry_Func 15 - page 2-564 */
(
  char *name,         /* name of directory */
  char *buff,         /* buffer for results */
  int num,            /* number of entries to read */
  int first,          /* starting with this entry */
  int length,         /* buffer length */
  image_handle ih
)

{
  FS_dir_block *res;

  debug("Read_dir_info: '%s' in image &%08x; buff at &%08x, length = %d; read %d entries starting at %d\n",
        name, (int)ih, (int)buff, length, num, first);

  res = scan_dir(name, buff, num, first, length, ih, store_info);

  debug("  - num_read = %d, next_to_read = %d\n",
         res->num_read, res->next_offset);

  return res;
}

/***************************************************************************/

image_handle NewFS_image_open       /* ImageEntry_Func 21 - page 2-567 */
(
  int fswh,
  int buff_size
)

  /* Note that NewFS ignores buff_size */

{
  icb *new_icb;

  debug("Image_open: fswh = %d, buffer size %d\n",
        fswh, buff_size);

 /* allocate an image control block */
  new_icb = check_alloc(sizeof(icb));

 /* note FileSwitch's handle for the image */
  new_icb->fswh = fswh;

 /* read the image's header into the icb */
  read_bytes(new_icb, (char*)&(new_icb->hdr), sizeof(image_hdr), 0);

 /* determine file size and store in the icb */
  _swix(OS_Args, I0|I1|O2,
        2,         /* read open file's extent */
        fswh,      /* file handle */
        &(new_icb->file_size)
       );

 /* need a pending stamp request to start with */
  new_icb->stamp_pending = TRUE;

 /* link new icb in at front of list of registered images */
  new_icb->next = image_list;
  image_list = new_icb;

  debug("  - assigned image handle &%08x\n", (int)new_icb);

 /* return NewFS's handle for the image */
  return new_icb;
}

/***************************************************************************/

int NewFS_image_close               /* ImageEntry_Func 22 - page 2-568 */
(
  image_handle ih
)

{
  debug("Image_close: image handle = &%08x\n", (int)ih);

{
  disc_rec drec;
  _kernel_oserror *err;

  err = _swix(ADFS_DescribeDisc, I0|I1,
              ":0",
              (int)&drec
             );

  if (err != NULL)
    debug("ADFS_DescribeDisc error: '%s'\n", err->errmess);
  else
  {
    debug("  - image stamp = %d, disc name = '%s'\n",
          (drec.disc_id_hi << 8)+drec.disc_id_lo,
          drec.disc_name
         );
  }
}
 /* ensure image file on media - just a test, this is *not* required */
{
_kernel_oserror *err;

err = _swix(OS_Args, I0|I1,
            255,
            ih->fswh
           );
if (err != NULL)
  debug("OS_Args 255 (%d) error: '%s'\n", ih->fswh, err->errmess);
}

 /* remove icb from list of registered images */
  {
    image_handle *prev = &image_list;

    while (*prev != ih)
      prev = &((*prev)->next);

    *prev = ih->next;
  }

 /* free icb */
  check_free(ih);

 /* return ok */
  return 0;
}

/***************************************************************************/

int NewFS_defect_list               /* ImageEntry_Func 25 - page 2-571 */
(
  int buff,
  int length,
  image_handle ih
)

{
  debug("Defect_list: image handle = &%08x, buffer at &%08x, length = %d\n",
        (int)ih, buff, length);

  global_errorV("NewFS does not support DefectList");

  return -1;
}

/***************************************************************************/

int NewFS_add_defect                /* ImageEntry_Func 26 - page 2-572 */
(
  int offset,
  image_handle ih
)

{
  debug("Add_defect: offset = %d in image &%08x\n", offset, (int)ih);

  global_errorV("NewFS does not support AddDefect");

  return -1;
}

/***************************************************************************/

int NewFS_read_boot_option          /* ImageEntry_Func 27 - page 2-572 */
(
  image_handle ih
)

{
  debug("Read_boot_option: image handle &%08x\n", (int)ih);

  debug("  - boot_option = %d\n", ih->hdr.boot_option);

  return ih->hdr.boot_option;
}

/***************************************************************************/

int NewFS_write_boot_option         /* ImageEntry_Func 28 - page 2-573 */
(
  int option,
  image_handle ih
)

{
  debug("Write_boot_option: option = %d for image handle &%08x\n",
        option, (int)ih);

  ih->hdr.boot_option = option;
  write_bytes(ih, (char*)&ih->hdr, sizeof(image_hdr), 0);

  return 0;
}

/***************************************************************************/

int NewFS_used_space_map            /* ImageEntry_Func 29 - page 2-574 */
(
  char *buff,
  int length,
  image_handle ih
)

{
  int block_size = ih->hdr.block_size;
  int image_size = ih->hdr.image_size;
  int file_size = ih->file_size;
  int image_bytes, image_bits, file_bytes;
  int i;

  debug("Used_space_map: image handle &%08x; buff = &%08x, length = %d\n",
        (int)ih, (int)buff, length);

  image_bits = round_up_to_block(ih, image_size)/block_size;
  image_bytes = image_bits >> 3;
  image_bits &= 7;

  file_bytes = (round_up_to_block(ih, file_size)/block_size) >> 3;

  for (i=0; i<image_bytes; i++)
  {
    if (i == length)
      return 0;
    buff[i] = 0xff;
  }

  buff[image_bytes] = 0;
  for (i=0; i<image_bits; i++)
    buff[image_bytes] |= (1 << i);

  for (i=image_bytes+1; i<=file_bytes; i++)
  {
    if (i == length)
      return 0;
    buff[i] = 0x00;
  }

  return 0;
}

/***************************************************************************/

FS_free_space *NewFS_read_free_space
(                                   /* ImageEntry_Func 30 - page 2-574 */
  image_handle ih
)

{
  int space_left = ih->file_size - ih->hdr.image_size;
  int largest = space_left - (sizeof(object_hdr) + sizeof(object_name) + 4);
   /* allows for maximum name size */

  debug("Read_free_space: image handle &%08x\n", (int)ih);

  if (largest < 1) largest = 1;
  largest = round_up_to_block(ih, largest) - ih->hdr.block_size;
   /* accounts for requirement that allocation is multiple of block size */

  space_info_PB.free_space = space_left;
  space_info_PB.largest_object = largest;
  space_info_PB.disc_size = ih->file_size;

  debug("  - free_space = %d, largest_object = %d, disc_size = %d\n",
        space_info_PB.free_space, space_info_PB.largest_object,
        space_info_PB.disc_size);

  return &space_info_PB;
}

/***************************************************************************/

int NewFS_namedisc                  /* ImageEntry_Func 31 - page 2-575 */
(
  char *name,
  image_handle ih
)

{
  debug("Namedisc: name image &%08x '%s'\n", (int)ih, name);

 /* An ADFS disc record contains space for a maximum of 10 characters ... */
  if (strlen(name) > 10)
  {
    global_errorV("Disc name must be at most 10 characters long");
    return -1;
  }

 /* copy to image header and write back to image */
  strncpy(ih->hdr.image_name, name, 10);
  write_bytes(ih, (char*)&ih->hdr, sizeof(image_hdr), 0);

  return 0;
}

/***************************************************************************/

int NewFS_stampimage                /* ImageEntry_Func 32 - page 2-576 */
(
  int type,
  image_handle ih
)

{
  debug("Stampimage: type = %d for image handle &%08x\n", type, (int)ih);

  ih->stamp_pending = TRUE;

  if (type == 1)  /* means "do it now!" */
    write_bytes(ih, (char*)&ih->hdr, sizeof(image_hdr), 0);
     /* slightly horrid - write_bytes(..) always checks stamp_image_pending,
         and will stamp the image if it is TRUE. It also checks to see if
         the write request was simply to rewrite the image header, and
         will not do this twice! */

  return 0;
}

/***************************************************************************/

  /* This typedef defines the structure used to communicate between
      NewFS_objectatoffset(..) and check_inside(..) */

typedef struct posstr
{
  int offset;
  int prev;
} posstr;

/***************************************************************************/

static BOOL check_inside(image_handle ih, int pos, void *handle)

  /* used by NewFS_objectatoffset */

{
  posstr *pstr = (posstr*)handle;

  if (pos > pstr->offset)       /* offset points into previous object */
    return TRUE;

  pstr->prev = pos;
  return FALSE;
}

/***************************************************************************/

int NewFS_objectatoffset            /* ImageEntry_Func 33 - page 2-577 */
(
  int pos,
  char *buff,
  int length,
  image_handle ih
)

{
  posstr pstr;

  debug("Objectatoffset: at %d in image &%08x; buff = &%08x, length = %d\n",
        pos, (int)ih, (int)buff, length);

 /* return 1 if inside image header */
  if (pos >= 0 && pos < sizeof(image_hdr))
  {
    debug("  - result = 1\n");
    return 1;
  }

 /* return 0 if outside image area */
  if (pos < 0 || pos >= ih->hdr.image_size)
  {
    debug("  - result = 0\n");
    return 0;
  }

 /* otherwise it must be inside some object */
  pstr.offset = pos;
  for_each_object(ih, check_inside, (void*)&pstr);

 /* copy name of relevant object to buff */
  read_obj_name(ih, pstr.prev);
  strncpy(buff, curr_obj_name.name, length);

  debug("  - object called '%s', result = 3\n", curr_obj_name.name);

  return 3;
}

/***************************************************************************/
